﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Threading;
using System.Collections;
using System.IO;
using System.Diagnostics;

namespace ProfilerPDS {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {

        private const double columnWidth = 140;

        private bool tracingOptions_disablevarobj_state = true;
        private int _numberOfEvents = 0;

        public static RoutedCommand StartDrawCommand = new RoutedCommand();

        ObservableCollection<DllModule> dllModulesOb = null;
        ObservableCollection<EventInfo> eventsInfoOb = null;
        ObservableCollection<ApplicationInfo> _appsInfoOb = null;
        private bool _canSave = false;
        private bool _canStartDraw = false;
        private bool _canDgShow = false;
        private ArrayList columnsCord= new ArrayList();
        private ArrayList rowsCord = new ArrayList();
        private string selectedApplication;
        private DataLayer _dl = null;
        private string _filepath;
        private string _filename;
        private Guid _profilerGuid = new Guid("9E2B38F2-7355-4C61-A54F-434B7AC266C0");

        public MainWindow() {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e) {
            zoomSliderY.ValueChanged += zoomSlider_ValueChanged;
            zoomSliderX.ValueChanged += zoomSlider_ValueChanged;
            _dl = new DataLayer();
        }

        
        #region Draw Function and Utilities

        private void clear()
        {
            columnsCord.Clear();
            rowsCord.Clear();
            drawAreaCanvas.Children.Clear();
            drawAreaCanvas.Height = 10;
            drawAreaCanvas.Width = 10;
        }

        private void draw() {
            GraphicsProvider gP = GraphicsProvider.Instance; //singleton class
            TextBlock timeTxtB = null;
            double offsetTimeY = 70;
            int rowIndex = 0, colIndex = 0;
            double maxW=0;
            //store the coordinate of column 0
            columnsCord.Add(new Point(180, 10));//start columns
            gP.addVerticalLine(180, 70, (_numberOfEvents + 1) * offsetTimeY, drawAreaCanvas);
            columnsCord.Add(new Point(360, 10));//first box
            colIndex++;
            rowsCord.Add(new Point(20,100));
            Color color;
            string tooltip;
            Connection c = null;
            Cross x = null;
            BoxShape box = null;
            Stack openBoxShapesExceptions = new Stack();
            Hashtable openBoxShapesFunctions_stacks = new Hashtable();
            Stack openBoxShapesFunctions = null;



            //Stack openBoxShapesFunctions = new Stack();
            Stack stack = null;
            bool drawRow = false;

            foreach (EventInfo item in  this.eventsInfoOb) {

                color = gP.getColor();
                switch (item.EventType) {
                    case EventType.New_Thread:
                        drawRow = true;
                        tooltip=item.NameThread;
                        color= gP.getNextColor();
                        stack = new Stack();
                        stack.Push(item.IdThread);

                        openBoxShapesFunctions = new Stack();
                        openBoxShapesFunctions_stacks.Add(item.IdThread, openBoxShapesFunctions);
                        openBoxShapesFunctions = null;

                        break;
                    case EventType.Destroy_Thread:
                        drawRow = true;
                        tooltip = item.NameThread;
                        gP.popColor();
                        openBoxShapesFunctions_stacks.Remove(item.IdThread);
                        break;
                    case EventType.Enter_Function:
                        drawRow = true;
                        gP.addVerticalLine(((Point)columnsCord[colIndex]).X, 70, (_numberOfEvents + 1) * offsetTimeY, drawAreaCanvas);

                        box = new BoxShape(item.IdObject, item.IdClass, BoxTypes.FunctionBox);
                        box.XPos = ((Point)columnsCord[colIndex]).X - box.Width / 2;
                        box.YPos = ((Point)rowsCord[rowIndex]).Y;
                        box.zIndex = 1;
                        box.Id = item.IdFunction;

                        BoxShape b = new BoxShape(item.IdObject, item.IdClass, BoxTypes.StaticObject);
                        b.add(((Point)columnsCord[colIndex]).X-b.Width/2, ((Point)columnsCord[colIndex]).Y, drawAreaCanvas);

                        openBoxShapesFunctions = (Stack)openBoxShapesFunctions_stacks[item.IdThread];
                        if(openBoxShapesFunctions != null)
                            openBoxShapesFunctions.Push(box);

                        openBoxShapesFunctions = null;
                        c = new Connection();
                        c.newConnection(((Point)columnsCord[colIndex-1]).X, ((Point)rowsCord[rowIndex]).Y, ((Point)columnsCord[colIndex]).X, ((Point)rowsCord[rowIndex]).Y, ConnectionType.FunctionCall,item.NameFunction);
                        c.zIndex = 3;
                        c.add(drawAreaCanvas);

                        columnsCord.Add(new Point(((Point)columnsCord[colIndex]).X + box.Width + columnWidth, 10));

                        if (maxW < (((Point)columnsCord[colIndex]).X + box.Width + columnWidth)) maxW = (((Point)columnsCord[colIndex]).X + box.Width + columnWidth);

                        colIndex++;
                        tooltip = item.NameFunction;

                        box = null;

                        break;
                    case EventType.Leave_Function:
                        drawRow = true;
                        tooltip = "";
                        
                        openBoxShapesFunctions = (Stack)openBoxShapesFunctions_stacks[item.IdThread];
                        if (openBoxShapesFunctions != null)
                        {
                            if (openBoxShapesFunctions.Count > 0)
                            {
                                box = (BoxShape)openBoxShapesFunctions.Pop();

                                c = new Connection();
                                c.newConnection(((Point)columnsCord[colIndex - 2]).X, ((Point)rowsCord[rowIndex]).Y, ((Point)columnsCord[colIndex - 1]).X, ((Point)rowsCord[rowIndex]).Y, ConnectionType.FunctionReturn, item.NameFunction);
                                c.zIndex = 3;
                                c.add(drawAreaCanvas);
                                colIndex--;
                                tooltip = item.NameFunction;


                                if (box != null)
                                {
                                    box.add(((Point)rowsCord[rowIndex - 1]).Y - box.YPos, drawAreaCanvas);

                                    //updateCanvasSize(((Point)columnsCord[colIndex]).X + 70, ((Point)rowsCord[rowIndex]).Y);

                                }

                                box = null;
                            }
                        }
                        
                        break;
                    case EventType.Exception_Thrown:
                        drawRow = true;
                        tooltip = item.ExceptionName;

                        x = new Cross();
                        x.Stroke=Brushes.Red; 
                        x.StrokeThickness=3.0;
                        x.X = ((Point)columnsCord[colIndex-1]).X;
                        x.Y = ((Point)rowsCord[rowIndex]).Y;


                        Canvas.SetZIndex(x, 5);
                        drawAreaCanvas.Children.Add(x);

                        x = null;

                        break;
                    case EventType.Exception_Catcher_Enter:
                        drawRow = true;

                        openBoxShapesFunctions = (Stack)openBoxShapesFunctions_stacks[item.IdThread];
                        if (openBoxShapesFunctions != null)
                        {
                            while (openBoxShapesFunctions.Count > 0)
                            {
                                string id = ((BoxShape)openBoxShapesFunctions.Peek()).Id;
                                if (id != item.IdFunction)
                                {
                                    box = (BoxShape)openBoxShapesFunctions.Pop();

                                    colIndex--;

                                    if (box != null)
                                    {
                                        box.add(((Point)rowsCord[rowIndex - 2]).Y - box.YPos, drawAreaCanvas);

                                        //updateCanvasSize(((Point)columnsCord[colIndex]).X + 70, ((Point)rowsCord[rowIndex]).Y);

                                    }

                                    box = null;
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }
                        openBoxShapesFunctions = null;
                        
                        tooltip = item.ExceptionName;

                        box = new BoxShape(item.ExceptionName, item.IdThread, BoxTypes.ExceptionBox);
                        box.XPos = ((Point)columnsCord[colIndex-1]).X - box.Width / 2;
                        box.YPos = ((Point)rowsCord[rowIndex]).Y;
                        box.zIndex = 2;

                        openBoxShapesExceptions.Push(box);

                        box = null;

                        break;
                    case EventType.Exception_Catcher_Leave:
                        drawRow = true;
                        tooltip = item.ExceptionName;

                        box = (BoxShape)openBoxShapesExceptions.Pop();

                        if (box != null)
                        {
                            box.add(((Point)rowsCord[rowIndex-1]).Y - box.YPos, drawAreaCanvas);

                            //updateCanvasSize(((Point)columnsCord[colIndex]).X + 70, ((Point)rowsCord[rowIndex]).Y);

                        }
                        
                        box = null;

                        break;
                    case EventType.Shutdown:
                        tooltip = "";
                        break;
                    case EventType.ObjectCreated:
                        if (tracingOptions_disablevarobj_state) drawRow = false;
                        else drawRow = true;
                        
                        tooltip = "New Variable";
                        break;
                    default:
                        tooltip = "ERROR";
                        break;
                }

                if (drawRow)
                {
                    //timestamp textblock
                    timeTxtB = new TextBlock();
                    timeTxtB.Text = item.TimeStamp;
                    timeTxtB.Foreground = Brushes.Black;
                    timeTxtB.Height = Double.NaN; //set Height to auto
                    timeTxtB.Width = Double.NaN;//set width to auto
                    Canvas.SetLeft(timeTxtB, ((Point)rowsCord[rowIndex]).X);
                    Canvas.SetTop(timeTxtB, ((Point)rowsCord[rowIndex]).Y);
                    drawAreaCanvas.Children.Add(timeTxtB);

                    LabelTS lblTs = null;

                    lblTs = new LabelTS(drawAreaCanvas, tooltip, item.EventType, ((Point)rowsCord[rowIndex]).X + 70, ((Point)rowsCord[rowIndex]).Y - 8, color);
                    //update row coord
                    Point p = new Point();
                    p.X = ((Point)rowsCord[rowIndex]).X;
                    p.Y = ((Point)rowsCord[rowIndex]).Y + offsetTimeY;
                    rowsCord.Add(p);
                    rowIndex++;

                    drawRow = false;

                    updateCanvasSize(maxW, ((Point)rowsCord[rowIndex]).Y);

                    //timeTxtB.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

                    //updateCanvasSize(((Point)rowsCord[maxColIndex]).X + timeTxtB.DesiredSize.Width, ((Point)rowsCord[rowIndex]).Y + timeTxtB.DesiredSize.Height);
                }
            }
        }



        private void updateCanvasSize(double width, double height) {
            if(drawAreaCanvas.Height<height){
                drawAreaCanvas.Height=height+20;
            }if(drawAreaCanvas.Width<width){
                drawAreaCanvas.Width = width+20;
            }
        }

        #endregion

        #region Command Bindings

        private void canStartDraw(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = _canStartDraw;
        }

        #region Start profile toolbar button clicked
        private void StartDrawHandle(object sender, ExecutedRoutedEventArgs e) {
            _canStartDraw = false;
            _canDgShow = true;
            //_dl.loadFromFile();
            startProfileApplication(_filepath, _filename);
            loadEvents(-1);

            //  draw();
            _canSave = true;

        }
        #endregion

        private void loadEvents(int id) {
            //populate the dll modules listbox
            List<DllModule> dllModulesList = _dl.getDllModulesList(null);
            this.dllModulesOb = new ObservableCollection<DllModule>(dllModulesList);

            //retrieve all events but populate the dataGrid only when the tab became active
            List<EventInfo> evtInfoList = _dl.getEvents(id);
            this.eventsInfoOb = new ObservableCollection<EventInfo>(evtInfoList);
            numbersOfEventsLbl.Content = eventsDg.Items.Count;


        }


        //open log menu item
        private void canOpen(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = true;
        }

        private void openHandle(object sender, ExecutedRoutedEventArgs e) {
            //MessageBox.Show("open");
            List<ApplicationInfo> appInfoList = _dl.getApplicationInfo();
            this._appsInfoOb = new ObservableCollection<ApplicationInfo>(appInfoList);
            if(appInfoList.Count>0){
                ApplicationChooserWindow appChooserW = new ApplicationChooserWindow();
                //appChooserW.DataContext = _appsInfoOb;
                appChooserW.applicationsDg.ItemsSource = _appsInfoOb;
                bool? choose=appChooserW.ShowDialog();
                if(choose==true){
                    ApplicationInfo appI= appChooserW.Selected;
                    if(appI!=null){
                        //MessageBox.Show(appI.Path);
                        this.loadEvents(appI.Id);
                        //set application name
                        selectedApplicationLbl.Content = appI.Name;
                        //set the number of events
                        if (tracingOptions_disablevarobj_state) {
                            _numberOfEvents = eventsInfoOb.Where(x => x.EventType != EventType.ObjectCreated).Count();
                            numbersOfEventsLbl.Content = _numberOfEvents;
                        } else {
                            _numberOfEvents = this.eventsInfoOb.Count;
                            numbersOfEventsLbl.Content = _numberOfEvents;
                        }
                        this.clear();
                        this.draw();
                        _canSave = true;
                        _canDgShow = true;//TODO Check again    
                    
                    }
                }
            
            }


            

            
            
        }

        private void canSave(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = _canSave;
        }

        private void saveHandle(object sender, ExecutedRoutedEventArgs e) {
            MessageBox.Show("Saved");
            _canSave = false;
        }

        private void canClose(object sender, CanExecuteRoutedEventArgs e) {
            //TODO check if a profile task is running
            e.CanExecute = true;
        }

        private void closeHandle(object sender, ExecutedRoutedEventArgs e) {
            this.Close();
        }

        #endregion

       

        private void searchApplication_Click(object sender, RoutedEventArgs e) {
            _canDgShow = false;
            Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
            //dlg.FileName = "Document"; // Default file name
            dlg.DefaultExt = ".txt"; // Default file extension
            dlg.Filter = "Applications (.exe)|*.exe"; // Filter files by extension

            // Show open file dialog box
            Nullable<bool> result = dlg.ShowDialog();
            string  filepath=null;
            string filename = null;
            // Process open file dialog box results
            if (result == true) {
                // Open document
                filepath = dlg.FileName;
                filename = System.IO.Path.GetFileNameWithoutExtension(filepath);
                applicationPathTxt.Text = filename;
                this.selectedApplication = filename;
                selectedApplicationLbl.Content = filename;
                
                _canStartDraw = true;
                _filepath = filepath;
                _filename = filename;
                
                

            } else {
                applicationPathTxt.Text = "Choose An Application";
                selectedApplicationLbl.Content = "Not Set";
                _canStartDraw = false;
            }
        }

        private void startProfileApplication(string appPath,string appName) {
            ProcessStartInfo psi = new ProcessStartInfo(appPath);

            //Enabling profiling
            if (psi.EnvironmentVariables.ContainsKey("COR_ENABLE_PROFILING") == true)
                psi.EnvironmentVariables["COR_ENABLE_PROFILING"] = "1";
            else
                psi.EnvironmentVariables.Add("COR_ENABLE_PROFILING", "1");

            //Setting profiler GUID
            if (psi.EnvironmentVariables.ContainsKey("COR_PROFILER") == true)
                psi.EnvironmentVariables["COR_PROFILER"] = "{9E2B38F2-7355-4C61-A54F-434B7AC266C0}";
            else
                psi.EnvironmentVariables.Add("COR_PROFILER", "{9E2B38F2-7355-4C61-A54F-434B7AC266C0}");
            //application name
            if (psi.EnvironmentVariables.ContainsKey("PROFILER_APPLICATION_NAME") == true)
                psi.EnvironmentVariables["PROFILER_APPLICATION_NAME"] = appName;
            else
                psi.EnvironmentVariables.Add("PROFILER_APPLICATION_NAME", appName);

            //path
            if (psi.EnvironmentVariables.ContainsKey("PROFILER_APPLICATION_PATH") == true)
                psi.EnvironmentVariables["PROFILER_APPLICATION_PATH"] = appPath;
            else
                psi.EnvironmentVariables.Add("PROFILER_APPLICATION_PATH", appPath);
            

            //db
            if (psi.EnvironmentVariables.ContainsKey("PROFILER_DATABASE_NAME") == true)
                psi.EnvironmentVariables["PROFILER_DATABASE_NAME"] = "1";
            else
                psi.EnvironmentVariables.Add("PROFILER_DATABASE_NAME", "1");


            //Starting profiling session
            //_currentSession = new ProfilingSession();
            //_currentSession.FK_Application = SelectedApplication.Id;
            //_currentSession.StartTime = DateTime.Now;

            psi.UseShellExecute = false;
            //IsTopMost = false;
            Process p = Process.Start(psi);
            p.WaitForExit();
            //IsTopMost = true;
            int exitCode=p.ExitCode;

            openHandle(null,null);


        }

        private void allEventsTab_GotFocus(object sender, RoutedEventArgs e) {
            if(_canDgShow==true){
                if (tracingOptions_disablevarobj_state) {
                    eventsDg.ItemsSource = this.eventsInfoOb.Where(x => x.EventType != EventType.ObjectCreated);
                } else {
                    eventsDg.ItemsSource = this.eventsInfoOb;
                }
                
            }
        }

        #region Toolbar
        private void zoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
            Slider slider= sender as Slider;

            if (lockZoomChb.IsChecked==true) {
                if (slider == zoomSliderX) {
                    zoomSliderY.Value = zoomSliderX.Value;
                }else{
                    zoomSliderX.Value = zoomSliderY.Value;
                
                }
            
            }

        }
        #endregion

        private void exportSeqDiaBtn_Click(object sender, RoutedEventArgs e) {
            int Height = (int)this.drawAreaCanvas.Height;
            int Width = (int)this.drawAreaCanvas.Width;

            RenderTargetBitmap bmp = new RenderTargetBitmap(Width, Height, 96, 96, PixelFormats.Pbgra32);
            bmp.Render(this.drawAreaCanvas);

            string file = this.selectedApplication+".png";

            string Extension = System.IO.Path.GetExtension(file).ToLower();

            BitmapEncoder encoder;
            if (Extension == ".gif")
                encoder = new GifBitmapEncoder();
            else if (Extension == ".png")
                encoder = new PngBitmapEncoder();
            else if (Extension == ".jpg")
                encoder = new JpegBitmapEncoder();
            else
                return;

            encoder.Frames.Add(BitmapFrame.Create(bmp));
            try {
                using (Stream stm = File.Create(file)) {
                    encoder.Save(stm);
                    MessageBox.Show("Sequence diagram correctly exported");
                }
            } catch (System.InvalidOperationException ex) {
                MessageBox.Show("Error while trying to export the sequence diagram\n"+ex.Message);
                
            }catch (System.NotSupportedException ex){
                MessageBox.Show("Error while trying to export the sequence diagram\n"+ex.Message);
            }
            
        }

        private void TracingOptions_DisableVarProf_Click(object sender, RoutedEventArgs e)
        {
            tracingOptions_disablevarobj_state = !tracingOptions_disablevarobj_state;
        }



        
    }
}
